package com.github.ylt.player;


import com.github.ylt.utils.ByteUtils;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.UnixDomainSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * mvp和ipc服务的实现
 */
public class Player {
    private ProcessBuilder builder;
    private String path;
    private int num;
    private OutputStream outputStream;
    private InputStream inputStream;
    private SocketChannel socketChannel;
    private UnixDomainSocketAddress address;
    private Selector selector;
    private Process start;
    private PlayState state = PlayState.PLAYING;
    private TransportStatus transportStatus = TransportStatus.OK;
    private String url;
    private String title;
    private List<String> event = Collections.synchronizedList(new ArrayList<>());

    private Player() {
    }
    private static class PlayerHolder {
        private static final Player INSTANCE = new Player();
    }

    public static Player getInstance() {
        return PlayerHolder.INSTANCE;
    }

    public void start() {
        num = new Random().nextInt(0, 9999);
        this.path = "/home/wutong/ipc-server" + num;
        if(this.builder == null){
            this.builder = new ProcessBuilder("mpv",
                    url,
                    "--input-ipc-server=" + this.path,
                    "--image-display-duration=inf",
                    "--idle=yes",
                    "--no-terminal",
                    "--on-all-workspaces",
                    "--hwdec=yes",
                    "--save-position-on-quit=yes",
                    "--script-opts=osc-timetotal=yes,osc-layout=bottombar,osc-title="+title+",osc-showwindowed=yes,osc-seekbarstyle=bar,osc-visibility=auto");
            try {
                this.start = this.builder.start();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                this.address = UnixDomainSocketAddress.of(this.path);
                this.selector = Selector.open();
                this.socketChannel = SocketChannel.open(address);
                this.socketChannel.configureBlocking(false);
                this.outputStream = Channels.newOutputStream(this.socketChannel);
                this.inputStream = Channels.newInputStream(this.socketChannel);
                this.socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                new IPCReaderThread(selector, start).start();
                this.state = PlayState.PLAYING;
                this.transportStatus = TransportStatus.OK;
            } catch (IOException e) {
                this.state = PlayState.STOPPED;
                this.transportStatus = TransportStatus.ERROR_OCCURRED;
                this.start.destroy();
                throw new RuntimeException(e);
            }
        }else{
            loadFile(url);
        }
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public TransportStatus getTransportStatus() {
        return this.transportStatus;
    }
    public PlayState getPlayState() {
        return state;
    }
    public void play() {
        this.state = PlayState.PLAYING;
        sendCommand("{\"command\": [\"set_property\", \"pause\", false],\"request_id\":1}\n");
    }

    public void pause() {
        this.state = PlayState.PAUSED_PLAYBACK;
        sendCommand("{\"command\": [\"set_property\", \"pause\", true],\"request_id\":2}\n");
    }

    public void stop() {
        this.state = PlayState.STOPPED;
        sendCommand("{\"command\": [\"stop\"]}\n");
    }

    public void title(String title) {
        sendCommand("{\"command\": [\"set_property\", \"title\", \"" + title + "\"],\"request_id\":3}\n");
    }

    public void mute(boolean mute) {
        sendCommand("{\"command\": [\"set_property\",\"mute\", \"" + (mute ? "yes" : "no") + "\"],\"request_id\":4}\n");
    }

    public void showText(String text) {
        sendCommand("{\"command\": [\"show-text\", \"" + text + "\", 1000],\"request_id\":5}\n");
    }

    public void loadFile(String path) {
        sendCommand("{\"command\": [\"load-file\", " + path + ",\"replace\"], \"request_id\":6}\n");
    }

    public void getPlayTime() {

    }

    public void playTime(long time) {
        if(time == -1){
            sendCommand("{\"command\": [\"get_property\", \"time-pos\"],\"request_id\":8}\n");
        }else{
            sendCommand("{\"command\": [\"set_property\", \"time-pos\", " + time + "],\"request_id\":9}\n");
        }

    }

    public void volume() {
        sendCommand("{\"command\": [\"get_property\", \"volume\"] ,\"request_id\":7}\n");
    }

    private void sendCommand(String command) {
        try {
            ByteBuffer wrap = ByteBuffer.wrap(command.getBytes(StandardCharsets.UTF_8));
            this.socketChannel.write(wrap);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void destroy() {
        if (this.start != null) {
            this.start.destroy();
        }
    }

    public String getCommandResult(CommandType type){
        String result = null;
        for (String s : this.event) {
            if(type.equals(CommandType.VOLUME)){
                Gson gson = new Gson();
                Map map = gson.fromJson(s, Map.class);
                result = (String) map.get("data");
            }
        }
        return result;
    }

    public String get(String requestId) {

        return "50.0";
    }
    public enum PlayState{
        STOPPED,
        PAUSED_PLAYBACK,
        PLAYING,
        TRANSITIONING,
        NO_MEDIA_PRESENT;
    }
    public enum TransportStatus{
        OK,
        ERROR_OCCURRED,
    }
    private class IPCReaderThread extends Thread implements Runnable {
        private final Selector selector;
        private final ByteBuffer buffer = ByteBuffer.allocate(1024);
        private final Process builder;

        public IPCReaderThread(Selector selector, Process builder) {
            super("ipc-reader-thread");
            this.selector = selector;
            this.builder = builder;
        }

        @Override
        public void run() {
            while (!this.isInterrupted()) {
                if (this.selector != null) {
                    try {
                        selector.select();
                        Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                        while (iterator.hasNext()) {
                            SelectionKey key = iterator.next();
                            if (key.isReadable()) {
                                SocketChannel channel1 = (SocketChannel) key.channel();
                                try {
                                    if (channel1.read(buffer) > 0) {
                                        buffer.flip();
                                        String s = ByteUtils.getInstance().bytesToString(buffer.array());
                                        if (event.size() >= 1000) {
                                            event.clear();
                                        }
                                        System.out.println(s);
                                        String[] split = s.split("\n");
                                        System.out.println("split = " + Arrays.toString(split));
                                        for (int i = 0; i < split.length - 1; i++) {
                                            if (!split[i].contains("request_id")) {
                                                continue;
                                            }
                                            event.add(split[i]);
                                        }
                                        buffer.clear();
                                    }
                                } catch (JsonSyntaxException e) {
                                    this.builder.destroy();
                                    channel1.close();
                                    throw new RuntimeException(e);
                                }
                            }
                        }
                    } catch (IOException e) {
                        this.builder.destroy();
                        throw new RuntimeException(e);
                    }

                }
            }
        }
    }

    public enum CommandType{
        PAUSE(2),
        PLAY(1),
        VOLUME(7);
        private final int value;

        private CommandType(int value){
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    }
}
